
import pytest
import allure
import time
from typing import Callable, Any, List
from selenium.common.exceptions import TimeoutException
from appium.webdriver.webelement import WebElement


class Assertions:
    """断言工具类"""

    def __init__(self, driver):
        self.driver = driver

    @staticmethod
    def assert_true(condition: bool, message: str = "条件不为真"):
        """断言条件为真"""
        with allure.step(f"断言: {message}"):
            assert condition, message

    @staticmethod
    def assert_false(condition: bool, message: str = "条件不为假"):
        """断言条件为假"""
        with allure.step(f"断言: {message}"):
            assert not condition, message

    @staticmethod
    def assert_equal(actual, expected, message: str = None):
        """断言两个值相等"""
        if message is None:
            message = f"期望: {expected}, 实际: {actual}"
        with allure.step(f"断言相等: {message}"):
            assert actual == expected, message

    @staticmethod
    def assert_not_equal(actual, expected, message: str = None):
        """断言两个值不相等"""
        if message is None:
            message = f"值不应该相等: {actual} == {expected}"
        with allure.step(f"断言不相等: {message}"):
            assert actual != expected, message

    @staticmethod
    def assert_contains(actual: str, expected_substring: str, message: str = None):
        """断言字符串包含子串"""
        if message is None:
            message = f"'{actual}' 应该包含 '{expected_substring}'"
        with allure.step(f"断言包含: {message}"):
            assert expected_substring in actual, message

    @staticmethod
    def assert_not_contains(actual: str, unexpected_substring: str, message: str = None):
        """断言字符串不包含子串"""
        if message is None:
            message = f"'{actual}' 不应该包含 '{unexpected_substring}'"
        with allure.step(f"断言不包含: {message}"):
            assert unexpected_substring not in actual, message

    @staticmethod
    def assert_element_displayed(element: WebElement, element_name: str = "元素"):
        """断言元素显示"""
        with allure.step(f"断言 {element_name} 显示"):
            assert element.is_displayed(), f"{element_name} 应该显示"

    @staticmethod
    def assert_element_not_displayed(element: WebElement, element_name: str = "元素"):
        """断言元素不显示"""
        with allure.step(f"断言 {element_name} 不显示"):
            assert not element.is_displayed(), f"{element_name} 不应该显示"

    @staticmethod
    def assert_element_enabled(element: WebElement, element_name: str = "元素"):
        """断言元素可用"""
        with allure.step(f"断言 {element_name} 可用"):
            assert element.is_enabled(), f"{element_name} 应该可用"

    @staticmethod
    def assert_element_disabled(element: WebElement, element_name: str = "元素"):
        """断言元素不可用"""
        with allure.step(f"断言 {element_name} 不可用"):
            assert not element.is_enabled(), f"{element_name} 应该不可用"

    def wait_for_condition(self, condition: Callable[[], bool], timeout: int = 10,
                           message: str = "条件未在指定时间内满足") -> bool:
        """等待条件满足"""
        end_time = time.time() + timeout
        while time.time() < end_time:
            if condition():
                return True
            time.sleep(0.5)
        return False

    def assert_condition_with_timeout(self, condition: Callable[[], bool], timeout: int = 10,
                                      message: str = "条件未在指定时间内满足"):
        """带超时的条件断言"""
        with allure.step(f"等待条件: {message}"):
            if not self.wait_for_condition(condition, timeout):
                pytest.fail(message)

    def assert_element_present_with_timeout(self, locator, timeout: int = 10,
                                            element_name: str = "元素"):
        """带超时的元素存在断言"""
        def condition():
            try:
                elements = self.driver.find_elements(*locator)
                return len(elements) > 0 and elements[0].is_displayed()
            except:
                return False

        self.assert_condition_with_timeout(
            condition, timeout, f"{element_name} 未在 {timeout} 秒内显示"
        )

    def assert_element_text(self, element: WebElement, expected_text: str,
                            element_name: str = "元素"):
        """断言元素文本"""
        actual_text = element.text
        self.assert_equal(actual_text, expected_text,
                          f"{element_name} 文本应该为 '{expected_text}'")

    def assert_element_contains_text(self, element: WebElement, expected_text: str,
                                     element_name: str = "元素"):
        """断言元素文本包含指定内容"""
        actual_text = element.text
        self.assert_contains(actual_text, expected_text,
                             f"{element_name} 文本应该包含 '{expected_text}'")

    @staticmethod
    def assert_list_length(items: List, expected_length: int, message: str = None):
        """断言列表长度"""
        if message is None:
            message = f"列表长度应该为 {expected_length}, 实际为 {len(items)}"
        with allure.step(f"断言列表长度: {message}"):
            assert len(items) == expected_length, message

    @staticmethod
    def assert_list_contains(items: List[Any], expected_item: Any, message: str = None):
        """断言列表包含某个元素"""
        if message is None:
            message = f"列表应该包含 {expected_item}"
        with allure.step(f"断言列表包含: {message}"):
            assert expected_item in items, message

    @staticmethod
    def soft_assert(assertion_func: Callable, *args, **kwargs):
        """软断言 - 收集多个断言错误"""
        try:
            assertion_func(*args, **kwargs)
            return True
        except AssertionError as e:
            return str(e)

    def verify_all_assertions(self, assertions: List[tuple]):
        """验证多个断言，收集所有错误"""
        errors = []
        for assertion in assertions:
            if isinstance(assertion, tuple):
                func, args, kwargs = assertion[0], assertion[1], assertion[2]
            else:
                func, args, kwargs = assertion, [], {}

            error = self.soft_assert(func, *args, **kwargs)
            if error is not True:
                errors.append(error)

        if errors:
            error_message = "多个断言失败:\n" + "\n".join(f"- {error}" for error in errors)
            pytest.fail(error_message)